package hydrap2p.proxy.manager;

import hydrap2p.ServiceLocation;
import hydrap2p.helper.ServiceConnector;
import hydrap2p.servicemanager.ServiceManager;
import hydrap2p.servicemanager.NameTaken;

import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.Random;

import org.apache.thrift.TException;
import org.apache.thrift.TProcessor;
import org.apache.thrift.server.TThreadPoolServer;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TServerTransport;

/**
 * This is a variant of ThriftServerThread that explicitly binds to the given port, and throws an error instead of trying again if it can't
 * @author Hufman
 *
 */
public class ThriftServerThreadBind extends Thread {
	
	private static final int PORTTRIES=50; 
	
	private final TThreadPoolServer server;
	private final String name;
	private final ServiceLocation location;
	
	public ThriftServerThreadBind(final TProcessor serverType, final String name) throws BindException, IOException, NameTaken
	{
		this(serverType, name, -1);
	}
	/**
	 * Creates an unstarted Thread that acts as a Thrift server
	 * @param serverType	The Thrift Processor object that should be listening
	 * @param name			The name that should be registered in the Manager for this server
	 * @param defaultPort	The default port that will be tried first. It will try up to 50 (PORTTRIES) other ports if the default port is unavailable
	 * @throws BindException	Thrown if no open ports could be found within the 50 tries
	 * @throws IOException		Thrown if the manager could not be contacted
	 * @throws NameTaken		Thrown if the given name is already registered in the Manager
	 */
	public ThriftServerThreadBind(final TProcessor serverType, final String name, final int port) throws BindException, IOException, NameTaken
	{
		// Bind to a preferred TCP port, or fallback to some random one
		Random ports = new Random();
		ServerSocket socket = new ServerSocket();
		
		socket.bind(new InetSocketAddress("localhost", port));
		
		this.name=name;

		if(socket.getLocalPort() == -1)
			throw new BindException();

		location = new ServiceLocation("localhost", port);
		
		TServerTransport tsocket = new TServerSocket(socket);
		TThreadPoolServer.Args args = new TThreadPoolServer.Args(tsocket);
		args.processor(serverType);
		server = new TThreadPoolServer(args);
		
	}
	
	/**
	 * Returns a copy of the ServiceLocation
	 * @return a copy of the ServiceLocation
	 */
	public ServiceLocation getLocation()
	{
		return new ServiceLocation(location);
	}
	
	/**
	 * Starts running the thread
	 */
	@Override
	public void run() {
		server.serve();
	}
	
	/**
	 * Stops the server nicely
	 * Also tries to unregister from the Manager
	 */
	public void shutdown() {
		ServiceManager.Client manager;
		try {
			manager = ServiceConnector.getServiceManager();
			if (manager!=null && name!=null)
				manager.delServiceLocation(name);
		} catch (TException e) {
			// Failed to notify the manager that we're shutting down, oh noes
		}
		server.stop();
	}

}
